# %%
import numpy as np
import matplotlib.pyplot as plt

"""
seed( ) 用于指定随机数生成时所用算法开始的整数值，如果使用相同的seed( )值，则每次生成的随即数都相同，
如果不设置这个值，则系统根据时间来自己选择这个值，此时每次生成的随机数因时间差异而不同
"""
np.random.seed(42)

N = 100  # 维度0, x有几行

# rand函数根据给定维度生成[0,1)之间的数据，包含0，不包含1
# 参数就是生成的维度(N,1) -> N*1
# 返回值为指定维度的array
x = np.random.rand(N, 1) * 5  # 随机x
y = 9.81 * x
# randn函数返回一个或一组样本，具有标准正态分布。
# 参数就是生成的维度(N,1) -> N*1
noise = 2 * np.random.randn(N, 1)
y_obs = y + noise  # 作为随机的y真实值

# %%
plt.scatter(x, y_obs, label="Observations")
plt.plot(x, y, c='r', label="True function")
plt.legend()
plt.show()


# %% 定义辅助函数

# helper functions
# w*x (y=w^t*x+b)
def f(w):
    return w * x


# 损失函数, 将误差平方后求合 / N -> 方差
def loss_function(e):
    L = np.sum(np.square(e)) / N
    return L


# 计算梯度(导数): &L/&w = 1/N * ∑(2(y-wx)(-x)) = 1/N * ∑(2ξx)
def dL_dw(e, w):
    # mean：求平均
    return -2 * np.mean(e * x)


# w 会更新 -> w_new = w_current - γ * &L/&w ，其中γ是学习率(0~1)

# the actual gradient descent
def gradient_descent(iter=100, gamma=0.1):
    """
    梯度下降
    :param iter: 迭代轮数
    :param gamma: 学习率
    :return: w的历史值，loss的历史值
    """
    # get starting conditions
    w = 10 * np.random.random()  # 随机初始化权重参数

    # 保存参数w的所有历史值
    params = []
    # 生成 iter行1列的 0填充的 数组
    loss = np.zeros((iter, 1))  # 记录损失值
    for i in range(iter):
        params.append(w)
        # 计算误差
        e = y_obs - f(w)  # (y_obs真实值-f(w)预测值)
        loss[i] = loss_function(e)
        # update parameters 更新权重参数
        w_news = w - gamma * dL_dw(e, w)  # 更新w
        w = w_news
    return params, loss


# %% 开始计算
params, loss = gradient_descent()

iter = 100
gamma = 0.1
# 随机初始化权重参数
w = 10 * np.random.randn()
params = []  # w
loss = np.zeros((iter, 1))  # loss(e)
for i in range(iter):
    params.append(w)
    e = y_obs - f(w)  # 求误差 (y_true-y_pred)
    loss[i] = loss_function(e)

    w_news = w - gamma * dL_dw(e, w)
    w = w_news

print(dL_dw(e, w))  # -1.0766942892814769e-14
plt.plot(loss)
plt.show()  # 损失值的图像一直在下降

params = np.array(params)
plt.plot(params)
plt.title('Gradient Descent')
plt.xlabel('w')
plt.show()  # 权重的变化

# 查看最后一个w，可以看到非常接近9.8
print(params[-1])  # 9.757142247178272
